1994-1996 by Oberon microsystems, Inc., Switzerland.
All rights reserved. No part of this publication may be reproduced in any form or by any means, without prior written permission by Oberon microsystems. The only exception is the free electronic distribution of the education version of Oberon/F (see the accompanying
copyright
notice
for details).
Oberon/F module interfaces and their descriptions in particular may not be used in other works without written permission.
Oberon microsystems, Inc.
Technoparkstrasse 1
CH-8005 Z
Switzerland
Oberon is a trademark of ETH Z
rich, Switzerland.
Oberon/F, Oberon/L, "Oberon by Example", "The Oberon Tribune", "Oberon Developer Forum", and "Drag & Pick" are trademarks of Oberon microsystems, Inc.
All other trademarks and registered trademarks belong to their respective owners.
Besides consuming stored text documents, the Oberon compiler can compile modules from anywhere in any displayed text document. If the beginning of a displayed text is also the beginning of a module, the command Dev->Compile can be used to compile the module. If the module begins somewhere in the middle of a displayed text, its beginning can be selected, e.g. by double
clicking on the keyword MODULE, and then the command Dev->Compile
Selection can be used.
To compile a list of modules at once, a list of module names needs to appear in some displayed text, e.g.
FormModels FormViews FormControllers FormCmds
By selecting the part of the list that should be considered by the compiler and by invoking the command Dev->Compile
Module
List, the listed modules are compiled consecutively. The process stops as soon as an erroneous module has been encountered. The compiler reports on the success or failure of compilations by writing into the system log. The log is a special text displayed in an auxiliary window. It can be opened using Info->Open
Log and cleared using Info->Clear
The log is a development tool, i.e. it should be used for debugging purposes and for development tools only. End-user applications are not expected to display a log. Whether or not a log window is opened upon startup of Oberon/F is determined by the configuration's Config module (in directory System/Mod). This module can be changed by the programmer as desired; by default it only contains one statement, which opens the log.
Dev->Open
Module
List is a convenient command which opens the module sources of one or several modules: select a module name, e.g. Config, and then execute Dev->Open
Module
List. This command can save you much time when you work with multiple subsystems (-> Subsystems and Global Modules) at the same time.
When compiling a newly created or a modified module, direct compilation of the displayed program text is recommended. In addition to writing to the system log, the compiler then also inserts error markers into the source text and places the caret after the first marker. Each marker represents an error flagged by the compiler in the source text. Normally, a marker is displayed as a crossed
out box. However, simply by clicking into it, a marker expands to display the corresponding error message. If the insertion point is directly behind a marker, it can be expanded via the command Dev->Toggle
Error
Mark (or more likely via its keyboard equivalent).
Windows:
At the bottom of a window there is a status bar. If the user clicks into an error marker, the error message is written into the status bar instead of expanding the error marker. The error marker can be forced to expand by a modifier-click or a double-click on the marker.
The command Dev->Next
Error can be used to skip forward to the next marker. Dev->Unmark
Errors removes all remaining markers from a text. However, the latter is rarely required: The compiler automatically removes all old markers when recompiling a text; and when storing a text, contained markers are filtered out, i.e. do not appear anymore when the text is opened again.
If a compiled module contains one or several errors, the text is scrolled to the first one. If this doesn't happen, the module was successfully compiled. In addition to this feedback, the compiler writes the number of errors found to the log, if there are errors. If the module interface has changed compared to a previous version, the changes are listed in the log as well.
The successful compilation of a module yields two files: a symbol file and a code file. A symbol file contains the information about a module's interface, and is used by the compiler to check imported modules for consistency. The code file represents the generated code (Intel 386 code on Windows, Motorola 68020 code on Macintosh). The contents of a code file is linked and loaded dynamically when needed, thus there is no need for a separate linker.
Symbol files are only needed during development (used by the compiler and the interface browser), they are not needed to run modules. A symbol file is a special encoding of a module's interface, i.e. of its exported constants, variables, types, and procedures. If the interface of a module is changed and the module recompiled, a new symbol file is written to disk. When compiling a module, the compiler reads the symbol files of all modules imported. This information is used to generate code, but also to type-check the correct use of imported identifiers. After the interface of a module has been modified, all modules importing it (i.e. all its clients) must be recompiled. Only those modules need to be recompiled which actually use a feature that has been changed. A mere addition of features does not invalidate the clients of a module: if you export a further procedure, for example, no clients need to be recompiled. This also holds for constants, variables, and types, but not for type-bound procedures. Addition of a type-bound procedure is not considered an extension, but rather a modification of an interface, and thus may invalidate clients.
Code files are produced, but never read during compilation. They are necessary to run modules, i.e. the dynamic (linking) loader must be able to read them.
See also modules
DevCompiler
DevCmds
, and
DevMarkers
Browsing Tools
A common cause of compile
time errors is the wrong use of interfaces. To quickly retrieve the actual definitions of items exported by modules, the browser may be used. The interface of a whole module is displayed when selecting the name of a module and executing Info->Interface. To display the definition of an individual item, e.g. a type or procedure, the qualified name of that item, i.e. module.item should be selected with the same command. The browser displays its output in a form that can directly be used as input for further browsing actions.
Two related commands, Info->Source and Info->Documentation, allow to look up an item's definition in a source file, or in an on-line documentation, respectively. They both work on selected module names as well as on names in the form module.item, just as the Info->Interface command.
The general text search commands Info->Search
Sources and Info->Search
Docu search a selected string in the available source files, respectively documentation files. For source files, this means in the global Mod directory and in each subsystems' Mod directory. For documentation files, this means in the global Manuals directory and in each subsystem's Docu directory. As a result of these commands, a new text is opened which contains a hyperlink for each file where the string occurs, and the count of how many times it occurs there. When the user clicks on the hyperlink, the corresponding file is opened and first instance of the string selected. With Text->Find
Again, the next instance can be found. Note that these commands may take up to several minutes to complete.
See also modules
DevBrowser
DevReferences
, and
StdSearch
Loading and Unloading Modules
Once a module successfully passes the compiler it can be loaded to the system and used. Once loaded, a module remains loaded if it isn't unloaded explicitly. The list of all currently loaded modules is displayed by Info->Loaded
Modules. In order to use a new version of a module that is already loaded, the module needs to be unloaded first. Command Dev->Unload takes a focused module source as input and tries to unload the corresponding module. Dev->Unload fails if the module is still in use, i.e. if it is imported by at least one other module that is still loaded. (In general, modules can only be released top down.) The command Dev->Unload
Module
List takes a selection as input, which must consist of a sequence of module names. You may directly use a selection in the text produced by Info->Loaded
Modules as input.
See also module
DevDebug
Executing Commands
There are several ways to execute commands (i.e. procedures exported by Oberon modules, intended for direct invocation by the user) within Oberon/F. A command name can be written to a text, as a string of the form "module.procedure", selected, and executed using Dev->Execute. An easier way is to insert a commander in front of the command name, using Tools->Insert
Commander. Clicking on a commander interprets the string following it as a sequence of commands, e.g.
"Dialog.Beep; DevDebug.ShowLoadedModules"
If the string consists of only one parameterless command, the string delimiters may be omitted, e.g.
Dialog.Beep
With such simple commands, it is possible to combine the unloading of an old version with the execution of a new version of a command (resp. of its module): modifier-click on the commander causes the command's module to be unloaded, and then the new module version is loaded and the procedure executed. Note that this "unload shortcut" only works for top-level modules, i.e. for modules which are not imported by any other modules.
Oberon/F tries to call the command Config.Setup when it is starting up. You can change Config in order to customize your Oberon/F configuration upon startup.
See also modules
DevDebug
and
Config
Debugging
When a run-time error occurs, e.g. a division by zero or some assertion trap, a trap window is opened. Such a window contains a text which shows the stack contents at the time when the trap occurred. An extract of such a trap text is shown below:
In the first line, the exception or trap number is given, e.g. "Index out of range" or "TRAP 0". Further below, a sequence of procedure activations is given, e.g. the last active procedure (where the trap occurred) was ObxTrap.Do, which had been called by StdInterpreter.CallProc, which had been called by StdInterpreter.Command, etc. Each procedure is marked with a small diamond mark to the left of its name. Clicking on this diamond mark produces a new window which shows the global variables of the module in which this procedure is defined, e.g.
The diamond mark to the right of a procedure opens the source of the module in which this procedure is defined, selects the statement which has been interrupted, and scrolls to this selection. Of course, this is only possible for modules whose source code is available.
But now let us go back to the stack display. The lines below a procedure's name show the parameters and local variables of the procedure, sorted alphabetically. The following example
str Dialog.String "String"
means that a local variable str of type Dialog.String had the value "String" when the trap occurred. After a pointer variable, a diamond mark allows to follow the pointer to the record to which it points, e.g. the pointer v
v Views.View [85D29730H]
can be followed (by clicking on the diamond mark) to the record
Such a display is opened in another window. The fields of a record are indicated by the preceding ".". On the first line, the path you have followed is indicated. If you have followed more than one dereferencing step, a diamond mark at the right end of this line lets you trace back again step by step.
When a trap occurs in a view implementation, e.g. in the view's Restore procedure, the error which caused it might later lead to another trap again, e.g. when the view becomes uncovered, its Restore procedure is called again. In the worst case, this may lead to a never-ending sequence of traps. Oberon/F prevents this by partially disabling a trapped view. Such a view is overlaid with a light grey pattern; its contents won't be restored again. However, the view's remaining behavior is still intact, i.e. it may be saved in a document. If saving leads to a trap, the view is turned into an alien; this is indicated by a cross overlaid over the view. The rest of the container document could still be saved, however.
Oberon/F distinguishes three categories of view traps:
- refresh: a trap in the view's Restore or RestoreMarks procedures
- save: a trap in the view's Internalize or Externalize procedures
- other: a trap in any other of the view procedures
If a trap in one of those categories occurs, this behavior will be disabled (i.e. the system won't call the procedures in this category anymore), behavior of the other categories remains intact. In the worst case, a view can lead to three traps, one for each category.
This means that erroneous views "degrade gracefully", by freezing only those behaviors that already have led to traps. If for some reason a view should be "unfrozen" again, the command Dev
Revalidate
View can be used.
An endless loop can be interrupted by pressing ctrl-break (Windows 95/NT) / command-option-. (Mac OS).
Each trap has a trap number associated with it. The following conventions are used:
Free
0 ..
Preconditions
20 ..
Postconditions
60 ..
Invariants 100 .. 120
Reserved 121 .. 124
Obsolete 125
Not Yet Implemented 126
Interface Procedure Called 127
You can use trap codes 0..19 freely in your programs, typically as temporary breakpoints during debugging. By convention, the other trap codes are generated by various assertions, i.e. statements which check a certain condition, and terminate the command if the condition is violated. Conditions which must be fulfilled upon entry of a procedure are called preconditions, conditions which must be fulfilled upon exit of a procedure are called postconditions, and conditions which must be fulfilled in between are called invariants. Most Oberon/F procedures check some preconditions. Typically, trap numbers are kept unique inside of a procedure. Thus, if a trap occurs, consult the documentation (or source, if available) of the trapped procedure. There you should get more information about the cause of the trap.
It should be noted that at no time during debugging the normal Oberon/F environment is left, there is no special "debugging mode" or "debugging environment"; everything is integrated instead!
Example:
ObxTrap
The Oberon/F debugger is a cross between a "post-mortem" debugger and a "run-time" debugger. It is invoked after a command has trapped (post-mortem), but it doesn't cause a termination of the Oberon/F environment (run-time). Some features, such as the Info->View
State command, which makes it possible to follow data structures starting from a selected view, are usually associated with run-time debuggers only.
It is typical for object-oriented programs that their control flows can become extremely convoluted and hard to follow. Thus following a program statement for statement (single step), by message sends, or by procedure calls in practice turns out to be unpractical for debugging large systems. Instead, Oberon/F uses a more effective debugging strategy:
Let errors become manifest as soon as possible.
Instead of waiting for some error to occur, and then trying to find one's way backward to the cause of the error, we attempted to flag errors as closely to their cause as possible. This is the only way to truly save debugging time. The language implementation follows the same strategy, by checking index overflows when accessing arrays, by checking NIL accesses when dereferencing a pointer, etc. In addition to these built-in checks, Oberon/L provides the standard procedure ASSERT, which allows to test for an arbitrary condition. If the condition is violated, a trap window is opened. Oberon/F procedures consequently use assertions e.g. at the beginning of a procedure to check whether its input is valid. This prevents that a procedure with illegal input may perform any damage to the rest of the system.
This stategy has proven itself again and again during the development of Oberon/F, and is strongly recommended for serious development work.
See also module
DevDebug
Distribution
If you develop add-on components (i.e. new subsystems) for Oberon/F, distribution simply means to distribute copies of your subsystem directory. You may not want to distribute the source texts, in order to protect your intellectual property rights. In this case you should not put them into the distribution copies. You may not want to make one or several of your module interfaces public, or none at all if your application is not meant to be extensible. In this case, you should not put the corresponding symbol files into the distribution copies. However, for all public modules you should put a documentation file into the subsystem's Docu directory. Furthermore, it is customary to put a Map file into the subsystem directory. A map contains a list of all public modules; the elements of the list are hyperlinks. In addition, the map may contain general information about the product, e.g. an electronic "flyer". If the subsystem is distributed together with its sources, a list of the involved modules should be put into the subsystem's Mod directory under the name list. It should list the modules starting with the module lowest in the module hierarchy and ending with the module highest in the module hierarchy, such that the list can be selected and the modules compiled with the Dev->Compile
Module
List command.
The previous paragraph has shown everything you need to do to distribute an add-on component, i.e. an extension in the form of a subystem. On the other hand, if you want to distribute a stand-alone application for some reason, you have two possibilities: In the first case, which is more suited for extensible applications, you provide the unlinked code files of your application and of Oberon/F, together with a minimal linked application to boot the whole software system. In the second case, which may be more suited for closed applications, you link everything (your code files as well as all Oberon/F code) into one large application.
Unlinked:
To make an unlinked stand-alone version you need to copy some files and directories from the Oberon directory to a new directory. What you need (and are allowed) to copy are:
The Code subdirectories in the directories Form, Host, Std, System, and Text,
the Rsrc subdirectories in the directories Form, Host, Std, System, and Text,
the Code and Rsrc subdirectories belonging to your own subsystems,
and the file OberonF.exe which may be renamed to the name of your new application.
Windows:
The batch-file CopyTo.bat creates all necessary directories and copies all these files. The new main directory must already exist and its full path must be supplied to the batch-file as a parameter (use Run... in the File-Manager File menu).
Linked:
For a linked version you need to use the linker to pack your code files together with the Oberon/F code files into a single application. See the
linker
documentation
for details. Note that the linker is only available in the full version of Oberon/F. After linking, you need to copy the new application to a new directory. You also need to copy the Rsrc subdirectories of the Form, Host, Std, System, Text, and of your own subsystems. If the resource files are missing, the application would still operate (kind of), but lose all string mappings, all dialog layouts, and all custom menus.
Both versions:
In addition, you need to create your own versions of the menu definition file and of the About... dialog (System/Rsrc/Menus and System/Rsrc/About). If your application is meant to be extensible, you also need to copy the symbol files (Sym/Xyz) of all modules whose interfaces should be public. This may or may not encompass the standard Oberon/F modules. Make sure the structure of your new directories matches the subdirectory structure in the original Oberon directory!
Windows:
Adapt the Help menu command, and the corresponding help text in an appropriate way.
Mac OS:
Edit the Help text in the System/Rsrc directory in an appropriate way.
To distribute software electronically over a medium which doesn't support binary distribution, e.g. over e-mail, a
standard
ASCII
encoder
is available.
Cross-Platform Issues
If you use Oberon/F both on Mac OS and on Windows, you'll regularly transfer documents between the two platforms. Code and symbol files cannot be transferred, since their formats are non-portable. To simplify the transfer of documents, you should configure the available tools (e.g. Apple's PC Exchange software) such that they map the Mac OS file type ("oODC") and file creator ("obnF") to the Windows file name suffix "ODC" and vice versa.
Furthermore, you may want to use fonts which are available on both platforms, either TrueType fonts or PostScript fonts if you have Adobe's Type Manager available. If you don't have equivalent fonts on both platforms, you can still read the text, with another font substituted for the correct one. This will likely result in visually unsatisfactory results.
As any documents, dialog boxes can be transferred between the two platforms. However, for best results you may want to fine-tune a dialog layout for the platform's controls before distributing it.
In order to minimize installation problems, it is recommended to limit module names to at most eight characters (not counting the prefix, which itself may be up to eight characters), and not to use module names which only differ in their use of small or capital letters.